package com.tools.middle.elasticsearch;

import co.elastic.clients.elasticsearch._types.mapping.Property;
import com.tools.common.container.MapEnum;
import com.tools.common.container.OwnPairs;
import com.tools.common.container.RowKit;
import com.tools.common.object.Note;
import com.tools.common.thread.Timeunit;

import java.util.*;

/**
 * ElasticSearch 索引操作配置项参数的包装类
 * */
@Note("ElasticSearch 索引操作配置项参数的包装类")
public final class ESIndicesBuilder {

    @Note("索引名称")
    private List<String> indicesNames;

    @Note("最新主分片数量")
    private int numberOfShards = 1;

    @Note("最新备份分片数量")
    private int numberOfReplicas = 1;

    @Note("默认 1 秒超时")
    private String timeout = "1s";

    @Note("连接到 Master 节点的默认超时时间")
    private String masterNodeTimeout = "1s";

    @Note("IK 支持的 analyzer 有: ik_smart（最粗粒度分词），ik_max_word（最细粒度分词）。" +
            "其余的可以在 resources/custom/analysis.txt 中查看")
    private String analyzer = "standard";

    @Note("IK 支持的 tokenizer 有: ik_smart（最粗粒度分词），ik_max_word（最细粒度分词）。" +
            "其余的可以在 resources/custom/analysis.txt 中查看")
    private String tokenizer = "standard";

    @Note("类型映射集合。key 为字段名称，value 为属性类型")
    private Map<String, Property> mappings;

    /* ***********************************************************************************
     *
     *         构造器
     *
     * **********************************************************************************
     * */

    public ESIndicesBuilder(String indicesName) {
        this.setIndicesNames(indicesName);
    }

    public ESIndicesBuilder(String... indicesNames) {
        this.setIndicesNames(indicesNames);
    }

    public ESIndicesBuilder(Iterable<String> indicesNames) {
        this.setIndicesNames(indicesNames);
    }

    public static ESIndicesBuilder of(String indicesNam) {
        return new ESIndicesBuilder(indicesNam);
    }

    public static ESIndicesBuilder of(String... indicesNames) {
        return new ESIndicesBuilder(indicesNames);
    }

    public static ESIndicesBuilder of(Iterable<String> indicesNames) {
        return new ESIndicesBuilder(indicesNames);
    }

    /* ***********************************************************************************
     *
     *         Getter / Setter
     *
     * **********************************************************************************
     * */

    List<String> getIndicesNames() {
        return indicesNames;
    }

    public List<String> getNewIndicesNames() {
        return (indicesNames == null || indicesNames.isEmpty())
                ? new ArrayList<>()
                : new ArrayList<>(indicesNames);
    }

    public String getFirstIndicesName() {
        return indicesNames.iterator().next();
    }

    public void setIndicesNames(String indicesName) {
        private_indicesNameNotEmpty(indicesName);
        this.indicesNames = new ArrayList<>(1);
        this.indicesNames.add(indicesName);
    }

    public void setIndicesNames(String... indicesNames) {
        if(indicesNames == null) throw new NullPointerException("请至少设置一个以上的索引");
        int length = indicesNames.length;
        if(length == 0) throw new IllegalArgumentException("请至少设置一个以上的索引");
        this.indicesNames = new ArrayList<>(length);
        for (String name : indicesNames) {
            private_indicesNameNotEmpty(name);
            this.indicesNames.add(name);
        }
    }

    public void setIndicesNames(Iterable<String> indicesNames) {
        if(indicesNames == null) throw new NullPointerException("请至少设置一个以上的索引");
        if(indicesNames instanceof List) {
            List<String> names = (List<String>) indicesNames;
            if(names.isEmpty()) throw new IllegalArgumentException("请至少设置一个以上的索引");
            for (String n : names) private_indicesNameNotEmpty(n);
            this.indicesNames = names;
            return;
        }
        int size = RowKit.getIterableSize(indicesNames);
        if(size == 0) throw new IllegalArgumentException("请至少设置一个以上的索引");
        this.indicesNames = new ArrayList<>(size);
        for (String name : indicesNames) {
            private_indicesNameNotEmpty(name);
            this.indicesNames.add(name);
        }
    }

    public int getNumberOfShards() {
        return numberOfShards;
    }

    public void setNumberOfShards(int numberOfShards) {
        this.numberOfShards = Math.max(numberOfShards, 1);
    }

    public int getNumberOfReplicas() {
        return numberOfReplicas;
    }

    public void setNumberOfReplicas(int numberOfReplicas) {
        this.numberOfReplicas = Math.max(numberOfReplicas, 1);
    }

    public String getTimeout() {
        return timeout;
    }

    public void setTimeout(String timeout) {
        if(timeout == null || timeout.isEmpty()) return;
        this.timeout = timeout;
    }

    public void setTimeout(long timeout, Timeunit timeunit) {
        if(timeunit == null) throw new NullPointerException("超时时间单位枚举实例为 null");
        this.timeout = timeunit.getHasUnitTime(timeout);
    }

    public String getMasterNodeTimeout() {
        return masterNodeTimeout;
    }

    public void setMasterNodeTimeout(String masterNodeTimeout) {
        if(masterNodeTimeout == null || masterNodeTimeout.isEmpty()) return;
        this.masterNodeTimeout = masterNodeTimeout;
    }

    public void setMasterNodeTimeout(long masterNodeTimeout, Timeunit timeunit) {
        if(timeunit == null) throw new NullPointerException("连接 Master 节点的超时时间单位枚举实例为 null");
        this.masterNodeTimeout = timeunit.getHasUnitTime(masterNodeTimeout);
    }

    public String getAnalyzer() {
        return analyzer;
    }

    public void setAnalyzer(String analyzer) {
        if(analyzer == null || analyzer.isEmpty()) return;
        this.analyzer = analyzer;
    }

    public String getTokenizer() {
        return tokenizer;
    }

    public void setTokenizer(String tokenizer) {
        if(tokenizer == null || tokenizer.isEmpty()) return;
        this.tokenizer = tokenizer;
    }

    Map<String, Property> getMappings() {
        return mappings;
    }

    public Map<String, Property> getNewMappings() {
        return (mappings == null || mappings.isEmpty())
                ? new HashMap<>()
                : new HashMap<>(mappings);
    }

    @Note("Property 可以通过静态 of 方法构建。如: Property.of(p -> p.text(t -> t))")
    public void setMappings(String field, Property type) {
        private_fieldNotEmpty(field);
        private_propertyTypeNotNull(type);
        this.mappings = new HashMap<>(2);
        this.mappings.put(field, type);
    }

    public void setMappings(Map<String, Property> mappings) {
        if(mappings == null || mappings.isEmpty()) return;
        this.mappings = new HashMap<>(MapEnum.calcInitSize(mappings.size()));
        for (Map.Entry<String, Property> e : mappings.entrySet()) private_addMapping(e.getKey(), e.getValue());
    }

    public void setMappings(OwnPairs<String, Property> mappings) {
        if(mappings == null || mappings.isEmpty()) return;
        this.mappings = new HashMap<>(MapEnum.calcInitSize(mappings.size()));
        for (Map.Entry<String, Property> e : mappings.entrySet()) private_addMapping(e.getKey(), e.getValue());
    }

    @Override
    public String toString() {
        return "ESIndicesBuilder{" +
                "indicesNames=" + indicesNames +
                ", numberOfShards=" + numberOfShards +
                ", numberOfReplicas=" + numberOfReplicas +
                ", timeout=" + timeout +
                ", masterNodeTimeout=" + masterNodeTimeout +
                ", analyzer=" + analyzer +
                ", tokenizer=" + tokenizer +
                ", mappings=" + mappings +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ESIndicesBuilder that = (ESIndicesBuilder) o;
        return Objects.equals(indicesNames, that.indicesNames);
    }

    @Override
    public int hashCode() {
        return Objects.hash(indicesNames);
    }

    /* ***********************************************************************************
     *
     *         链式方法
     *
     * **********************************************************************************
     * */

    public ESIndicesBuilder indicesNames(String indicesName) {
        this.setIndicesNames(indicesName);
        return this;
    }

    public ESIndicesBuilder indicesNames(String... indicesNames) {
        this.setIndicesNames(indicesNames);
        return this;
    }

    public ESIndicesBuilder indicesNames(Iterable<String> indicesNames) {
        this.setIndicesNames(indicesNames);
        return this;
    }

    public ESIndicesBuilder numberOfShards(int numberOfShards) {
        this.setNumberOfShards(numberOfShards);
        return this;
    }

    public ESIndicesBuilder numberOfReplicas(int numberOfReplicas) {
        this.setNumberOfReplicas(numberOfReplicas);
        return this;
    }

    public ESIndicesBuilder timeout(String timeout) {
        this.setTimeout(timeout);
        return this;
    }

    public ESIndicesBuilder timeout(long timeout, Timeunit timeunit) {
        this.setTimeout(timeout, timeunit);
        return this;
    }

    public ESIndicesBuilder masterNodeTimeout(String masterNodeTimeout) {
        this.setMasterNodeTimeout(masterNodeTimeout);
        return this;
    }

    public ESIndicesBuilder masterNodeTimeout(long masterNodeTimeout, Timeunit timeunit) {
        this.setMasterNodeTimeout(masterNodeTimeout, timeunit);
        return this;
    }

    public ESIndicesBuilder analyzer(String analyzer) {
        this.setAnalyzer(analyzer);
        return this;
    }

    public ESIndicesBuilder tokenizer(String tokenizer) {
        this.setTokenizer(tokenizer);
        return this;
    }

    public ESIndicesBuilder mappings(String field, Property type) {
        this.setMappings(field, type);
        return this;
    }

    public ESIndicesBuilder mappings(Map<String, Property> mappings) {
        this.setMappings(mappings);
        return this;
    }

    public ESIndicesBuilder mappings(OwnPairs<String, Property> mappings) {
        this.setMappings(mappings);
        return this;
    }

    /* ***********************************************************************************
     *
     *         自定义方法
     *
     * **********************************************************************************
     * */

    public boolean useMappings() {
        return mappings != null && !mappings.isEmpty();
    }

    public ESIndicesBuilder addIndicesName(String indicesName) {
        private_indicesNameNotEmpty(indicesName);
        private_initIndicesNameList(1);
        this.indicesNames.add(indicesName);
        return this;
    }

    public ESIndicesBuilder addIndicesName(String... indicesNames) {
        int length = (indicesNames == null) ? 0 : indicesNames.length;
        if(length == 0) return this;
        private_initIndicesNameList(length);
        for (String n : indicesNames) {
            private_indicesNameNotEmpty(n);
            this.indicesNames.add(n);
        }
        return this;
    }

    public ESIndicesBuilder addIndicesName(Iterable<String> indicesNames) {
        int size = (indicesNames == null) ? 0 : RowKit.getIterableSize(indicesNames);
        if(size == 0) return this;
        private_initIndicesNameList(size);
        for (String n : indicesNames) {
            private_indicesNameNotEmpty(n);
            this.indicesNames.add(n);
        }
        return this;
    }

    public ESIndicesBuilder modifyIndicesName(int index, String indicesName) {
        if(indicesNames != null && !indicesNames.isEmpty() && !private_indexCheck(index, indicesNames.size())) this.indicesNames.set(index, indicesName);
        private_indicesNameNotEmpty(indicesName);
        return this;
    }

    public ESIndicesBuilder clearIndicesNames() {
        if(indicesNames == null || indicesNames.isEmpty()) return this;
        this.indicesNames.clear();
        return this;
    }

    public ESIndicesBuilder removeIndicesName(int index) {
        if(indicesNames == null || indicesNames.isEmpty()) return this;
        if(private_indexCheck(index, indicesNames.size())) return this;
        this.indicesNames.remove(index);
        return this;
    }

    public ESIndicesBuilder removeIndicesName(String indicesName) {
        if(indicesNames == null || indicesNames.isEmpty()) return this;
        this.indicesNames.remove(indicesName);
        return this;
    }

    public ESIndicesBuilder removeIndicesNames(String... indicesNames) {
        if(indicesNames == null || indicesNames.length == 0 || this.indicesNames == null || this.indicesNames.isEmpty()) return this;
        for (String s : indicesNames) this.indicesNames.remove(s);
        return this;
    }

    public ESIndicesBuilder removeIndicesNames(Iterable<String> indicesNames) {
        if(indicesNames == null || this.indicesNames == null || this.indicesNames.isEmpty()) return this;
        for (String s : indicesNames) this.indicesNames.remove(s);
        return this;
    }

    public int indicesNamesSize() {
        return (indicesNames == null) ? 0 : indicesNames.size();
    }

    public String getIndicesName(int index) {
        if(indicesNames == null || indicesNames.isEmpty()) return null;
        if(private_indexCheck(index, indicesNames.size())) return null;
        return indicesNames.get(index);
    }

    public boolean containsIndicesName(String indicesName) {
        return indicesNames != null && !indicesNames.isEmpty() && this.indicesNames.contains(indicesName);
    }


    public ESIndicesBuilder addMapping(String field, Property type) {
        private_fieldNotEmpty(field);
        private_propertyTypeNotNull(type);
        private_initMappings(1);
        this.mappings.put(field, type);
        return this;
    }

    public ESIndicesBuilder addMappings(Map<String, Property> mappings) {
        if(mappings == null || mappings.isEmpty()) return this;
        private_initMappings(mappings.size());
        for (Map.Entry<String, Property> e : mappings.entrySet()) private_addMapping(e.getKey(), e.getValue());
        return this;
    }

    public ESIndicesBuilder addMappings(OwnPairs<String, Property> mappings) {
        if(mappings == null || mappings.isEmpty()) return this;
        private_initMappings(mappings.size());
        for (Map.Entry<String, Property> e : mappings.entrySet()) private_addMapping(e.getKey(), e.getValue());
        return this;
    }

    public ESIndicesBuilder clearMappings() {
        if(mappings == null || mappings.isEmpty()) return this;
        this.mappings.clear();
        return this;
    }

    public ESIndicesBuilder removeMapping(String field) {
        if(mappings == null || mappings.isEmpty()) return this;
        this.mappings.remove(field);
        return this;
    }

    public ESIndicesBuilder removeMappings(String... fields) {
        if(fields == null || fields.length == 0) return this;
        if(mappings == null || mappings.isEmpty()) return this;
        for (String f : fields) this.mappings.remove(f);
        return this;
    }

    public ESIndicesBuilder removeMappings(Iterable<String> fields) {
        if(fields == null) return this;
        if(mappings == null || mappings.isEmpty()) return this;
        for (String f : fields) this.mappings.remove(f);
        return this;
    }

    public Property getType(String field) {
        if(mappings == null || mappings.isEmpty()) return null;
        return mappings.get(field);
    }

    public boolean containsType(String field) {
        if(mappings == null || mappings.isEmpty()) return false;
        return this.mappings.containsKey(field);
    }

    public int typeSize() {
        return (mappings == null) ? 0 : mappings.size();
    }

    /* ***********************************************************************************
     *
     *         私有逻辑
     *
     * **********************************************************************************
     * */

    @Note("索引名称非空校验")
    private static void private_indicesNameNotEmpty(String indicesName) {
        if(indicesName == null || indicesName.isEmpty()) throw new NullPointerException("索引名称不能为空");
    }

    @Note("字段名称非空校验")
    private static void private_fieldNotEmpty(String field) {
        if(field == null || field.isEmpty()) throw new NullPointerException("字段名称不能为空");
    }

    @Note("字段类型实例非空校验")
    private static void private_propertyTypeNotNull(Property type) {
        if(type == null) throw new NullPointerException("字段类型不能为 null");
    }

    @Note("初始化索引名称集合")
    private void private_initIndicesNameList(int initSize) {
        if(indicesNames == null) this.indicesNames = new ArrayList<>(initSize);
    }

    @Note("初始化类型映射集合")
    private void private_initMappings(int initSize) {
        if(mappings == null) this.mappings = new HashMap<>(MapEnum.calcInitSize(initSize));
    }

    @Note("批量添加字段类型映射时，添加每一个的逻辑")
    private void private_addMapping(String field, Property type) {
        private_fieldNotEmpty(field);
        private_propertyTypeNotNull(type);
        this.mappings.put(field, type);
    }

    @Note("判断所以是否是错误的索引")
    boolean private_indexCheck(int index, int size) {
        return index < 0 || index >= size;
    }

}
